package ConwaysGameOfLife;

import java.awt.Color;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Random;

/**
 *
 * @author Bryan
 */

/*
 This class is a mediator and controller of the Cell World
 It is responsible for calculating all cell deaths/births as well as interactions
 between GUI components and other important components. 

 Welcome to the mothership.
 */
public class WorldControlMediator implements WorldControlInterface {

    //User Interface Varaibles
    private ColorPallete brushColorPallete;
    private ColorPallete worldColorPallete;
    private WorldSettings worldSettings;
    private JDesktopInternal worldDisplay;

    //World Variables
    private Cell[][] worldArray;
    private Color worldColor = new Color(55, 151, 232);
    private Color cellColor = new Color(0, 102, 102);
    private final Color defaultWorldColor = new Color(55, 151, 232);
    private final Color defaultBrushColor = new Color(0, 102, 102);
    private boolean worldInitialized = false;
    private boolean worldPaused = false;
    private int worldSpeedDelay = 500;
    private int initializationDensity = 50;
    private boolean simulationClear = false;

    //Cell-Live/Death evaluation variables
    private ArrayList<Cell> livingCells = new ArrayList<Cell>();
    private ArrayList<Cell> possibleBirths = new ArrayList<Cell>();
    private ArrayList<Cell> livingCellsToAdd = new ArrayList<Cell>();
    
    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new MainDisplay(new WorldControlMediator()).setVisible(true);
            }
        });
    }

    //Sets specific cell as alive and adds it to a temporary living list
    //"livingCellsToAdd" is added to livingCells at evaluation step
    @Override
    public void birthCell(Cell c, ArrayList<Cell> livingCellsToAdd) {
        if (!c.isAlive()) {
            if (!worldPaused) {
                c.setAlive(true);
                livingCellsToAdd.add(c);
            } else {
                livingCells.add(c);
                c.setAlive(true);
            }
            
        }
        
    }
    
    @Override
    //updates UI colors
    public void updateColors() {
        worldColor = worldColorPallete.getCurrentColor();
        cellColor = brushColorPallete.getCurrentColor();
        worldSettings.getBrushColorButton().setBackground(cellColor);
        worldSettings.getWorldColor().setBackground(worldColor);
        worldDisplay.setBackgroundColor(worldColor);
    }

    //Defaults the UI colors
    @Override
    public void defaultWorldColors() {
        worldColorPallete.setCurrentColor(defaultWorldColor);
        brushColorPallete.setCurrentColor(defaultBrushColor);
        updateColors();
    }

    //Returns a cell from a specified point in an array
    //accomodates for world wrap.
    @Override
    public Cell getCell(Point p, Cell[][] worldArray) {
        if (p.x >= worldArray.length) {
            p.x = 0;
        }
        if (p.x < 0) {
            p.x = worldArray.length - 1;
        }
        if (p.y >= worldArray[p.x].length) {
            p.y = 0;
        }
        if (p.y < 0) {
            p.y = worldArray[p.x].length - 1;
        }
        return worldArray[p.x][p.y];
    }
    
    @Override
    public Cell[][] initializeWorld() {
        setWorldInitialized(true);
        worldArray = new Cell[worldSettings.getWorldWidth()][worldSettings.getWorldHeight()];
        //fills world array with cells
        for (int i = 0; i < worldArray.length; i++) {
            for (int j = 0; j < worldArray[i].length; j++) {
                worldArray[i][j] = new Cell(new Point(i, j), this, getBrushColor(), false);
                //sets cells as alive based on percentage chance (initialization density)
                if (worldSettings.isRandomlyInitialize()) {
                    Random number = new Random();
                    if (number.nextInt(100) < initializationDensity) {
                        birthCell(worldArray[i][j], livingCellsToAdd);
                    }
                } else {
                    worldArray[i][j].setAlive(false);
                }
            }
        }
        //Tells all cells to find their neighbors after all are initializaed
        for (int i = 0; i < worldArray.length; i++) {
            for (int j = 0; j < worldArray[i].length; j++) {
                worldArray[i][j].findAdjacentCells();
            }
        }
        //Thread start indicates beginning of the world run
        new Thread(this).start();
        return worldArray;
    }
    
    @Override
    public void clearSimulation() {
        simulationClear = true;
    }
    
    @Override
    public void togglePause() {
        worldPaused = !worldPaused;
    }
    
    @Override
    //evaluates all cells and potential cells for birth/death
    public void worldRoll() {
        
        if (!simulationClear) {
            possibleBirths = getPossibleBirths();
            ArrayList<Cell> tempBirthCells = getCellsToBirth(possibleBirths);
            ArrayList<Cell> tempKillCells = getCellsToKill(livingCells);
            ArrayList<Cell> tempCellsToRemain = getCellsRemaining(livingCells);
            //merge all cells that will be alive on next turn
            tempBirthCells.addAll(tempCellsToRemain);
            tempBirthCells.addAll(livingCellsToAdd);
            //purge living cells to add to avoid conflicts!
            livingCellsToAdd.clear();
            for (Cell cell : tempKillCells) {
                cell.setAlive(false);
            }
            //sets Cells that will be alive to be, well, alive.
            for (Cell cell : tempBirthCells) {
                cell.setAlive(true);
            }
            //final step! move new living cells to the live world for display
            livingCells = tempBirthCells;
        } else {
            //this clears all living cells in case "clear world" was selected
            for (Cell cell : livingCells) {
                cell.setAlive(false);
            }
            livingCells.clear();
            simulationClear = false;
        }
        
    }

    //returns a list of all cells that will remaing living at next step
    @Override
    public ArrayList<Cell> getCellsRemaining(ArrayList<Cell> possibles) {
        ArrayList<Cell> returnList = new ArrayList<>();
        for (Cell cell : possibles) {
            //if 2-3 neighbors the cell remains alive
            if (cell.reportNumberOfLivingNeighbors() == 2 || cell.reportNumberOfLivingNeighbors() == 3) {
                returnList.add(cell);
            }
        }
        return returnList;
        
    }

    //returns list of all cells that need to die at next step
    @Override
    public ArrayList<Cell> getCellsToKill(ArrayList<Cell> possibles) {
        ArrayList<Cell> returnList = new ArrayList<>();
        for (Cell cell : possibles) {
            //if less than two or more than 3 neighbors, cell dies.
            if (cell.reportNumberOfLivingNeighbors() < 2 || cell.reportNumberOfLivingNeighbors() > 3) {
                returnList.add(cell);
            }
        }
        return returnList;
    }

    //returns a list of all cells that will be birthed at next step
    @Override
    public ArrayList<Cell> getCellsToBirth(ArrayList<Cell> possibles) {
        ArrayList<Cell> returnList = new ArrayList<>();
        for (Cell cell : possibles) {
            //if exactly three neighbors a new cell will spawn here
            if (cell.reportNumberOfLivingNeighbors() == 3) {
                returnList.add(cell);
            }
            
        }
        return returnList;
    }
    
    @Override
    public void run() {
        
        while (worldInitialized) {
            while (!worldPaused && worldInitialized) {
                worldRoll();
                try {
                    Thread.sleep(worldSpeedDelay);
                } catch (Exception e) {
                }
                
            }
            try {
                Thread.sleep(worldSpeedDelay);
            } catch (Exception e) {
            }
        }
        
    }

    //returns a list of all dead cells surrounding live cells
    public ArrayList getPossibleBirths() {
        //clear last list
        for (Cell old : possibleBirths) {
            old.setEnlisted(false);
        }
        possibleBirths = new ArrayList<Cell>();
        
        for (Cell livingCell : livingCells) {
            Cell[] neighbors = livingCell.getAdjacentCells();
            for (Cell possibleCell : neighbors) {
                //if a surrounding cell is not alive AND is not currently in
                //the list of potential cells
                if (!possibleCell.isAlive() && !possibleCell.isEnlisted()) {
                    possibleBirths.add(possibleCell);
                    possibleCell.setEnlisted(true);
                }
            }
        }
        return possibleBirths;
    }

    //Getters and setters from here on down, nothing interesting to see here!
    @Override
    public int getWorldSpeedDelay() {
        return worldSpeedDelay;
    }
    
    @Override
    public void setWorldSpeedDelay(int worldSpeedDelay) {
        this.worldSpeedDelay = worldSpeedDelay;
    }
    
    @Override
    public JDesktopInternal getWorldDisplay() {
        return worldDisplay;
    }
    
    @Override
    public void setWorldDisplay(JDesktopInternal worldDisplay) {
        this.worldDisplay = worldDisplay;
    }
    
    @Override
    public void setWorldArray(Cell[][] worldArray) {
        this.worldArray = worldArray;
    }
    
    @Override
    public ArrayList<Cell> getLivingCells() {
        return livingCells;
    }
    
    @Override
    public void setLivingCells(ArrayList<Cell> livingCells) {
        this.livingCells = livingCells;
    }
    
    @Override
    public int getInitializationDensity() {
        return initializationDensity;
    }
    
    @Override
    public void setInitializationDensity(int initializationDensity) {
        this.initializationDensity = initializationDensity;
    }
    
    @Override
    public boolean isWorldPaused() {
        return worldPaused;
    }
    
    @Override
    public void setWorldPaused(boolean worldPaused) {
        this.worldPaused = worldPaused;
    }
    
    @Override
    public boolean isWorldInitialized() {
        return worldInitialized;
    }
    
    @Override
    public void setWorldInitialized(boolean worldInitialized) {
        this.worldInitialized = worldInitialized;
    }
    
    @Override
    public Color getDefaultWorldColor() {
        return defaultWorldColor;
    }
    
    @Override
    public Color getDefaultBrushColor() {
        return defaultBrushColor;
    }
    
    @Override
    public int getDefaultWorldLength() {
        return worldArray.length;
    }
    
    @Override
    public int getDefaultWorldWidth() {
        return worldArray[0].length;
    }
    
    @Override
    public Color getBrushColor() {
        return cellColor;
    }
    
    @Override
    public Color getWorldColor() {
        return worldColor;
    }
    
    @Override
    public void toggleWorldPallete() {
        worldColorPallete.setVisible(true);
    }
    
    @Override
    public void toggleBrushPallete() {
        brushColorPallete.setVisible(true);
    }
    
    @Override
    public void setBrushColorPallete(ColorPallete brushColorPallete) {
        this.brushColorPallete = brushColorPallete;
    }
    
    @Override
    public void setWorldColorPallete(ColorPallete worldColorPallete) {
        this.worldColorPallete = worldColorPallete;
    }
    
    @Override
    public void setWorldSettings(WorldSettings worldSettings) {
        this.worldSettings = worldSettings;
    }
    
    @Override
    public void setWorldColor(Color worldColor) {
        this.worldColor = worldColor;
    }
    
    @Override
    public void setBrushColor(Color brushColor) {
        this.cellColor = brushColor;
    }
    
    @Override
    public ArrayList<Cell> getLivingCellsToAdd() {
        return livingCellsToAdd;
    }
    
    @Override
    public void setLivingCellsToAdd(ArrayList<Cell> livingCellsToAdd) {
        this.livingCellsToAdd = livingCellsToAdd;
    }
    
    @Override
    public Cell[][] getWorld() {
        return worldArray;
    }
    
}
